package com.fuze.questionnaire.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.fuze.questionnaire.common.Advice;
import com.fuze.questionnaire.common.CustomException;
import com.fuze.questionnaire.domain.dto.UserResultDTO;
import com.fuze.questionnaire.domain.entity.BasicUserInformation;
import com.fuze.questionnaire.domain.entity.Problem;
import com.fuze.questionnaire.domain.entity.UserResult;
import com.fuze.questionnaire.mapper.BasicUserInformationMapper;
import com.fuze.questionnaire.mapper.OptionsMapper;
import com.fuze.questionnaire.mapper.ProblemMapper;
import com.fuze.questionnaire.mapper.UserResultMapper;
import com.fuze.questionnaire.service.UserResultService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;

/**
 * @author sepc_
 * @date 10/21/2021 16:35
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class UserResultServiceImpl implements UserResultService {

    final UserResultMapper userResultMapper;

    final ProblemMapper problemMapper;

    final OptionsMapper optionsMapper;

    final BasicUserInformationMapper basicUserInformationMapper;

    final StringRedisTemplate stringRedisTemplate;


    @Override
    public List<String> queryAnswer(Integer userId, Integer type) {
        String userIdStr = String.valueOf(userId);
        BasicUserInformation userById = basicUserInformationMapper.selectById(userId);
        if (userById == null) {
            throw new CustomException(Advice.USER_NOT_EXIST);
        }
        // 查询出缓存中的临时数据
        ArrayList<String> result = new ArrayList<>();
        String userResultJson = stringRedisTemplate.opsForValue().get(userIdStr);
        if (StringUtils.isBlank(userResultJson)) {
            return result;
        }
        Map<String, String> map = JSONObject.parseObject(
                JSON.parseObject(userResultJson).toJSONString(),
                new TypeReference<LinkedHashMap<String, String>>() {
                }
        );
        // 查询数据库中的问题，需要根据sorted确定问题的顺序
        List<Problem> list = new LambdaQueryChainWrapper<>(problemMapper)
                .eq(Problem::getProjectTypeId, type)
                .list();
        list.stream()
                .sorted(Comparator.comparing(Problem::getSorted).reversed())
                .forEach(p -> {
                    if (map.containsKey(String.valueOf(p.getId()))) {
                        result.add(map.get(String.valueOf(p.getId())));
                    }
                });
        return result;
    }

    /**
     * 这里提交的数据只是提交到了Redis
     *
     * @param resultForms 用户答案
     * @param userId      用户id
     * @return 是否提交成功
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public Boolean submitList(List<UserResultDTO> resultForms, Integer userId) {
        // 查询Redis中是否存有数据
        String userIdStr = String.valueOf(userId);
        Boolean hasKey = stringRedisTemplate.hasKey(userIdStr);
        if (Boolean.TRUE.equals(hasKey)) {
            // redis 中有数据，说明是更新操作
            String json = stringRedisTemplate.opsForValue().get(userIdStr);
            // 获取原有的数据
            Map<String, String> map = JSON.parseObject(
                    JSON.parseObject(json).toJSONString(),
                    new TypeReference<LinkedHashMap<String, String>>() {
                    }
            );
            resultForms.forEach(u -> map.put(String.valueOf(u.getProblemId()), u.getOptionsResult()));
            // 然后更新
            // int update = userResultMapper.updateBatchSelective(userResults);
            stringRedisTemplate.opsForValue().set(userIdStr, JSON.toJSONString(map));
            return true;
        }
        // 缓存中没有，则批量插入数据
        // int insert = userResultMapper.insertList(userResults);
        Map<String, String> map = new LinkedHashMap<>();
        resultForms.forEach(u -> map.put(String.valueOf(u.getProblemId()), u.getOptionsResult()));
        stringRedisTemplate.opsForValue().set(userIdStr, JSON.toJSONString(map));
        return true;
    }

    /**
     * 根据用户id把redis中的数据存到数据库
     *
     * @param userId 用户id
     * @return 是否提交成功
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public Boolean submitAll(Integer userId) {
        String userIdStr = String.valueOf(userId);
        Boolean hasKey = stringRedisTemplate.hasKey(userIdStr);
        Map<String, String> map;
        if (Boolean.TRUE.equals(hasKey)) {
            //说明Redis中有数据
            String json = stringRedisTemplate.opsForValue().get(userIdStr);
            // 获取原有的数据
            map = JSON.parseObject(
                    JSON.parseObject(json).toJSONString(),
                    new TypeReference<LinkedHashMap<String, String>>() {
                    }
            );
            map.forEach((k, v) -> {
                Integer problemKey = Integer.valueOf(k);
                UserResult one = new LambdaQueryChainWrapper<UserResult>(userResultMapper)
                        .select()
                        .eq(UserResult::getUserId, userId)
                        .eq(UserResult::getProblemId, problemKey)
                        .one();
                if (one != null) {
                    one.setOptionsResult(v);
                    int update = userResultMapper.updateById(one);
                    if (update != 1) {
                        throw new CustomException(Advice.FAILED_INSERT);
                    }
                }
                int insert = userResultMapper.insert(new UserResult(userId, problemKey, v));
                if (insert != 1) {
                    throw new CustomException(Advice.FAILED_INSERT);
                }
            });
        } else {
            throw new CustomException(Advice.FAILED_INSERT);
        }
        return Boolean.TRUE;
    }
}
